// Copyright (C) 1995 Robert Todd Thomas
// Use is governed by the MiscKit license

/******************************************************************
 * CLASS:			MiscArrowButtonCell
 *
 *	See the header for more information on this class.
 *
 * This object is included in the MiscKit by permission from the author
 * and its use is governed by the MiscKit license, found in the file
 * "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
 * for a list of all applicable permissions and restrictions.
 *******************************************************************/

#import "wraps.h"
#import <dpsclient/psops.h>
#import <apps/InterfaceBuilder.h>
#import "MiscArrowButtonCell.h"

// These #defines are used for versioning instances of this class, so that
// you will always be able to read any previously archived version. If you
// make changes to the class's ivars, bump the version number up one and 
// make the appropriate changes to the read: method.

#define MISC_ABC_VERSION 1
#define MISC_ABC_CLASSNAME "MiscArrowButtonCell"


@implementation MiscArrowButtonCell

+ initialize
{
	if (self == [MiscArrowButtonCell class])
		[self setVersion: MISC_ABC_VERSION];
	
	return self;
}



// The initializing methods just call the designated initializer
// and give some default values.

- init
{
	return [self initTextCell: "Left" altTitle: "Right"];
}



- initTextCell: (const char *)aString
{
	return [self initTextCell: aString altTitle: "Right"];
}



// The desinated initializer for this class.

- initTextCell: (const char *)aString altTitle: (const char *)altString
{
	[super initTextCell: aString];
	
	[self setAltTitle: altString];
	[self setState: 0];
	[self setBordered: NO];
	[self setShowsStateBy: NX_NONE];
	[self setHighlightsBy: NX_NONE];
	[self setArrowAlignment: MISC_ARROW_ABSOLUTE];
	[self setType: NX_TOGGLE];
	
	return self;
}



// Since the stringValue in Button does not do much, it now
// returns the currently selected text.

- (const char *)stringValue
{
	if ([self state] == 0)
		return (const char *)[self title];
	else
		return (const char *)[self altTitle];
}



// I believe this method is supposed to calculate the minimum size needed to 
// fit the currently displayed contents  (contents, altContents, and the arrow)
// in the cellframe.

- calcCellSize: (NXSize *)theSize inRect: (NXRect *)theRect
{
  float  contentsWidth = [ [self font] getWidthOf: [self title] ];
  float  altContentsWidth = [ [self font] getWidthOf: [self altTitle] ];
  
	// first calculate the width needed to draw all text and the arrow
	  
  	theSize->width = 0.0;
	if ([self arrowAlignment] == MISC_ARROW_RELATIVE)
	{
		theSize->width += contentsWidth;
		theSize->width += altContentsWidth;
	 }
	else	// absolute alignment
	{
		if (contentsWidth > altContentsWidth)
			theSize->width += contentsWidth * 2.0;
		else
			theSize->width += altContentsWidth * 2.0;
	 }
	 
	theSize->width += cellHeight + 10.0;					
	
	// now the height (which will usually be cellHeight)
	
	theSize->height = cellHeight;
	
	if ([ [self font] pointSize] > cellHeight)
		theSize->height = [ [self font] pointSize];
	 		
	return self;
}



// As far as I can tell (which may not be all that far) is that this 
// function is only used by the Button's IBEditor to tell how large
// the editor should be.

- getTitleRect : (NXRect *)theRect
{
  float  size = [ [self font] pointSize];
  float  theY;
  float  maxWidth = theRect->size.width/2.0;
				
	// this is a hack so you can double click on the altTitle in IB

	if ([self state])
		return [self getAltTitleRect: theRect];

	[self setAlignment: NX_LEFTALIGNED];	// used for the IBEditor
		
	theY = theRect->origin.y + theRect->size.height/2.0 - size/2.0;
	
	if ([self arrowAlignment] == MISC_ARROW_RELATIVE)
	  	maxWidth = [ [self font] getWidthOf: [self title] ] + 10.0;  	
		
	NXSetRect (theRect, theRect->origin.x, theY-2.0, maxWidth, size+2.0);		   	

	return self;
}



// If the state is 1 (altContents selected), then this method will be
// called. It returns the location for editor to appear when editing 
// the altContents.

- getAltTitleRect: (NXRect *)theRect
{
  float  size = [ [self font] pointSize];
  float  newX, newY, newWidth;
	
	[self setAlignment: NX_RIGHTALIGNED];	// used for the IBEditor

	newX = theRect->origin.x + theRect->size.width/2.0; 			 
	newY = theRect->origin.y + theRect->size.height/2.0 - size/2.0;
	newWidth = theRect->size.width/2.0;
	
	NXSetRect (theRect, newX, newY-2.0, newWidth, size+2.0);		   	
	
	return self;
}



// New method to set the alignment of the arrow. MISC_ARROW_ABSOLUTE aligns
// the arrow in the center of the cellFrame, where MISC_ARROW_RELATIVE
// centers the arrow between the contents and altContents.

- setArrowAlignment: (int)alignment
{
	arrowAlignment = alignment;
	return self;
}



- (int)arrowAlignment
{
	return arrowAlignment;
}



// This method is used to draw only the parts of the cell that
// do change when the state changes. Therefore, only the black arrow
// and the text are drawn here.

- drawInside: (const NXRect *)cellFrame inView: controlView
{
  float  gray;
  float  size = [ [self font] pointSize];
		    
	// draw the arrow

	if ([self arrowAlignment] == MISC_ARROW_RELATIVE)
	{
	  NXRect  relativeFrame;
	  float  contentsWidth = [ [self font] getWidthOf: [self title] ];
  	  float  altContentsWidth = [ [self font] getWidthOf: [self altTitle] ];

		// since it the arrow is drawn relative to the text, a little
		// calculation is needed
		
		relativeFrame.origin.x = cellFrame->origin.x + contentsWidth;
		relativeFrame.size.width = cellFrame->size.width - 
				(contentsWidth + altContentsWidth);

		PSABdrawarrow (relativeFrame.origin.x, cellFrame->origin.y + 3.0, 
				relativeFrame.size.width, cellFrame->size.height - 6.0, 
				(int)[self state], [self isEnabled]);
	 }
	else
		// draw the arrow in the center of the cellFrame
		
		PSABdrawarrow (cellFrame->origin.x, cellFrame->origin.y + 3.0, 
				cellFrame->size.width, cellFrame->size.height - 6.0, 
				(int)[self state], [self isEnabled]);
	
	// draw the left and right hand text
	
	[ [self font] set];
			
	if ([self title] != NULL)
	{	
	  float  theY;
	  			
		if ([self state] || ![self isEnabled])
			gray = NX_DKGRAY;
		else 
			gray = 0.0;

		// calculate the placement of the contents and print it
		
		theY = cellFrame->origin.y + cellFrame->size.height/2.0 + size/3.0;			

		PSABshowstring (cellFrame->origin.x + 3.0, theY, gray, [self title]);
	 }
		 
	if ([self altTitle] != NULL)
	{	
	  float  theX, theY;
	  float  strWidth = [ [self font] getWidthOf: [self altTitle] ];
	  			
		if ([self state] && [self isEnabled])
			gray = 0.0;
		else
			gray = NX_DKGRAY;
		
		// calculate the placement of the altContents and print it
		
		theX = cellFrame->origin.x+(cellFrame->size.width - strWidth - 3.0);
		theY = cellFrame->origin.y + cellFrame->size.height/2.0 + size/3.0;
					
		PSABshowstring (theX, theY, gray, [self altTitle]);		 	
	 }
		 	
	return self;
}



// This part of the drawing displays only the parts that don't change
// often, which includes the diamond that encloses the arrow, and the
// border when I get around to adding one. 

- drawSelf: (const NXRect *)cellFrame inView: controlView
{		  
	// if transparent draw the background white (when in IB) and 
	// same as the background when in an app (or testing interface)
	
	if ([self isTransparent])
	{	
		if ([NXApp respondsTo: @selector(isTestingInterface)])
			if ([NXApp isTestingInterface])
				PSsetgray (NX_LTGRAY);
			else
				PSsetgray (1.0);
		else
			PSsetgray (NX_LTGRAY);
		
		NXRectFill (cellFrame);

		return self;
	 }
	
	// set cellHeight since some of the calculations that are in other
	// methods have to know the height of the cell (this is probably the
	// wrong way to go about this)
	
	cellHeight = cellFrame->size.height;
	
	// draw the border or bezel, then call drawInside
	
	if ([self isBordered])
		NXDrawButton (cellFrame, cellFrame);
		
	PSgsave();

	// have to flip drawing if we are drawing into a flipped view
			
	if ([controlView isFlipped])
		PSABflipme (cellFrame->size.height + (cellFrame->origin.y * 2.0));
	
	if ([self arrowAlignment] == MISC_ARROW_RELATIVE)
	{
	  NXRect  relativeFrame;
	  float  contentsWidth = [ [self font] getWidthOf: [self title] ];
  	  float  altContentsWidth = [ [self font] getWidthOf: [self altTitle] ];

		relativeFrame.origin.x = cellFrame->origin.x + contentsWidth;
		relativeFrame.size.width = cellFrame->size.width - 
				(contentsWidth + altContentsWidth);
		
		PSABdrawdiamond (relativeFrame.origin.x, cellFrame->origin.y + 3.0,
				relativeFrame.size.width, cellFrame->size.height - 6.0);
	 }
	else
		PSABdrawdiamond (cellFrame->origin.x, cellFrame->origin.y + 3.0, 
			cellFrame->size.width, cellFrame->size.height - 6.0);
		
	PSgrestore();				

	// draw the rest of the cell
				
	[self drawInside: cellFrame inView: controlView];
	 
	return self;
}



// Archiving methods

- read: (NXTypedStream *)stream
{
	int  version;
	
	[super read: stream];
	version = NXTypedStreamClassVersion (stream, MISC_ABC_CLASSNAME);
	
	switch (version)
	{
	  case 0:
	  	// First version had no ivars, but set any new ivars to a useable
		// value.
	  	arrowAlignment = MISC_ARROW_ABSOLUTE;	
	  	break;
	  case 1:
	  	// Current version.
		NXReadType (stream, @encode(unsigned int), &arrowAlignment);
		break;
	  default:
	  	break;
	 }

	return self;
}



- write: (NXTypedStream *)stream
{
	[super write: stream];
	
	// You can also keep versioning here just in case you would like 
	// to back to an older version, but I don't.
	NXWriteType (stream, @encode(unsigned int), &arrowAlignment);
	return self;
}

@end


/******************************************************************
  CHANGES:
  	1. Now use setArrowAlignment/arrowAlignment to set/get whether the
 	 the arrow is relative or absolutely aligned. Added arrowAlignment 
	 instance var.
   October 1, 1994:
	2. Added archive versioning (+initialize).
	
 *******************************************************************/
 

 